﻿using CacheManager.Core;
using K9Nano.Extensions;
using Microsoft.Extensions.Options;

namespace K9Nano.Caching.CacheManager;

internal class TypedCache<TCachedValue> : ITypedCache<TCachedValue>
{
    private readonly ICacheManager<TCachedValue> _cache;
    private readonly CachingOptions _options;
    private readonly string _region;

    public TypedCache(ICacheManager<TCachedValue> cache, IOptions<CachingOptions> options)
    {
        _cache = cache;
        _options = options.Value;
        _region = CreateRegion();
    }

    private string CreateRegion()
    {
        var valueType = typeof(TCachedValue);
        if (valueType.IsGenericType)
        {
            throw new NotSupportedException("TCachedValue can not be a Generic Type");
        }

        if (valueType == typeof(string))
        {
            throw new NotSupportedException("TCachedValue can not be a string");
        }

        if (string.IsNullOrWhiteSpace(valueType.FullName))
        {
            throw new NotSupportedException("FullName of type TCachedValue can not be null");
        }

        if (valueType.TryGetCustomAttribute<CacheAttribute>(false, out var attr))
        {
            return attr!.Region;
        }

        var region = valueType.FullName.Replace('.', ':');
        if (!string.IsNullOrEmpty(_options.Prefix))
        {
            var prefix = _options.Prefix + ':';
            if (!region.StartsWith(prefix))
            {
                region = prefix + _region;
            }
        }

        return region;
    }

    private CacheItem<TCachedValue> CreateCacheItem(string key, TCachedValue value, TimeSpan? slidingTimeout,
        TimeSpan? absoluteTimeout)
    {
        if (slidingTimeout == null && absoluteTimeout != null)
        {
            return new CacheItem<TCachedValue>(key, _region, value, ExpirationMode.Absolute, absoluteTimeout.Value);
        }

        if (slidingTimeout != null && absoluteTimeout == null)
        {
            return new CacheItem<TCachedValue>(key, _region, value, ExpirationMode.Sliding, slidingTimeout.Value);
        }

        return new CacheItem<TCachedValue>(key, _region, value);
    }

    private CacheItem<TCachedValue> CreateCacheItem(TCachedValue value, TimeSpan? slidingTimeout,
        TimeSpan? absoluteTimeout)
    {
        if (slidingTimeout == null && absoluteTimeout != null)
        {
            return new CacheItem<TCachedValue>(_region, value, ExpirationMode.Absolute, absoluteTimeout.Value);
        }

        if (slidingTimeout != null && absoluteTimeout == null)
        {
            return new CacheItem<TCachedValue>(_region, value, ExpirationMode.Sliding, slidingTimeout.Value);
        }

        return new CacheItem<TCachedValue>(_region, value);
    }

    public bool Add(string key, TCachedValue value, TimeSpan? slidingTimeout = null, TimeSpan? absoluteTimeout = null)
    {
        var item = CreateCacheItem(key, value, slidingTimeout, absoluteTimeout);
        return _cache.Add(item);
    }

    public bool Add(TCachedValue value, TimeSpan? slidingTimeout = null, TimeSpan? absoluteTimeout = null)
    {
        var item = CreateCacheItem(value, slidingTimeout, absoluteTimeout);
        return _cache.Add(item);
    }

    public void Put(string key, TCachedValue value, TimeSpan? slidingTimeout = null, TimeSpan? absoluteTimeout = null)
    {
        var item = CreateCacheItem(key, value, slidingTimeout, absoluteTimeout);
        _cache.Put(item);
    }

    public void Put(TCachedValue value, TimeSpan? slidingTimeout = null, TimeSpan? absoluteTimeout = null)
    {
        var item = CreateCacheItem(value, slidingTimeout, absoluteTimeout);
        _cache.Put(item);
    }

    public TCachedValue Update(string key, Func<TCachedValue, TCachedValue> updateValue)
    {
        return _cache.Update(key, _region, updateValue);
    }

    public TCachedValue Update(Func<TCachedValue, TCachedValue> updateValue)
    {
        return _cache.Update(_region, updateValue);
    }

    public TCachedValue Get(string key)
    {
        return _cache.Get(key, _region);
    }

    public TCachedValue Get()
    {
        return _cache.Get(_region);
    }

    public void Expire(string key, DateTime absoluteExpiration)
    {
        _cache.Expire(key, _region, absoluteExpiration);
    }

    public void Expire(DateTime absoluteExpiration)
    {
        _cache.Expire(_region, absoluteExpiration);
    }

    public void Expire(string key, TimeSpan slidingExpiration)
    {
        _cache.Expire(key, _region, slidingExpiration);
    }

    public void Expire(TimeSpan slidingExpiration)
    {
        _cache.Expire(_region, slidingExpiration);
    }

    public void Clear()
    {
        _cache.ClearRegion(_region);
    }

    public bool Exists(string key)
    {
        return _cache.Exists(key, _region);
    }

    public bool Exists()
    {
        return _cache.Exists(_region);
    }

    public bool Remove(string key)
    {
        return _cache.Remove(key, _region);
    }

    public bool Remove()
    {
        return _cache.Remove(_region);
    }

    public TCachedValue AddOrUpdate(string key, TCachedValue addValue, Func<TCachedValue, TCachedValue> updateValue, TimeSpan? slidingTimeout = null,
        TimeSpan? absoluteTimeout = null)
    {
        var item = CreateCacheItem(key, addValue, slidingTimeout, absoluteTimeout);
        return _cache.AddOrUpdate(item, updateValue);
    }

    public TCachedValue AddOrUpdate(TCachedValue addValue, Func<TCachedValue, TCachedValue> updateValue, TimeSpan? slidingTimeout = null,
        TimeSpan? absoluteTimeout = null)
    {
        var item = CreateCacheItem(addValue, slidingTimeout, absoluteTimeout);
        return _cache.AddOrUpdate(item, updateValue);
    }

    public TCachedValue GetOrAdd(string key, TCachedValue value)
    {
        return _cache.GetOrAdd(key, _region, (k, r) => value);
    }

    public TCachedValue GetOrAdd(TCachedValue value)
    {
        return _cache.GetOrAdd(_region, (r) => value);
    }

    public TCachedValue GetOrAdd(string key, Func<string, TCachedValue> valueFactory)
    {
        return _cache.GetOrAdd(key, _region, (k, r) => valueFactory(key));
    }

    public TCachedValue GetOrAdd(Func<string, TCachedValue> valueFactory)
    {
        return _cache.GetOrAdd(_region, (key) => valueFactory(key));
    }

    public bool TryGetOrAdd(string key, Func<string, TCachedValue> valueFactory, out TCachedValue? value)
    {
        var result = _cache.TryGetOrAdd(key,
            _region,
            (k, r) => valueFactory(key),
            out var valueItem);

        value = result ? valueItem : default;
        return result;
    }

    public bool TryGetOrAdd(Func<string, TCachedValue> valueFactory, out TCachedValue? value)
    {
        return TryGetOrAdd(_region, valueFactory, out value);
    }

    public bool TryUpdate(string key, Func<TCachedValue, TCachedValue> updateValue, out TCachedValue value)
    {
        return _cache.TryUpdate(key, _region, updateValue, out value);
    }

    public bool TryUpdate(Func<TCachedValue, TCachedValue> updateValue, out TCachedValue value)
    {
        return _cache.TryUpdate(_region, updateValue, out value);
    }
}
